using Assets.Scripts;
using Assets.Scripts.Atmospherics;
using Assets.Scripts.Inventory;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
using VisualmodBep;
using VisualmodBep.Gui;
using Weather;

namespace StationeersServerPreference;

/**
 * In-game GUI
 * @ref https://docs.unity3d.com/Manual/GUIScriptingGuide.html
 * @ref https://docs.unity3d.com/cn/current/Manual/GUIScriptingGuide.html
 */
public class SspGui : GuiBase<SspGui> {
    #region Difficulty
    private FloatComponent BreathingRateDiv;
    private FloatComponent FoodDecayRateDiv;
    private FloatComponent HungerRateDiv;
    private FloatComponent HydrationRateDiv;
    private FloatComponent HygieneRateDiv;
    private FloatComponent JetpackRateDiv;
    private FloatComponent LungDamageRateDiv;
    private FloatComponent MiningYieldDiv;
    private FloatComponent MoodRateDiv;
    private FloatComponent OfflineMetabolismDiv;
    private FloatComponent RobotBatteryRateDiv;
    private FloatComponent WeatherLanderDamageRateDiv;

    private SelectComponent RespawnConditionDiv;
    #endregion

    #region Terminal
    private Texture2D windowTexNormal;
    private Texture2D windowTexTerminal;

    private FloatComponent gasModifyMolesDiv;
    private FloatComponent gasModifyTemperatureDiv;

    private GUIStyle copyLabelStyle;
    #endregion

    #region Prefabs
    private int prefabDisplayMode;
    private SelectComponent prefabDisplayModeDiv;
    private NavbarComponent[] prefabPanelDivs;

    private Dictionary<string, string> prefabDisplayNames = new Dictionary<string, string>();

    private class PrefabSubpanelSetting : VisualmodBep.Helper.AllInstance<PrefabSubpanelSetting> {
        public string title; // ns "UI.Panel."
        public int xCount;

        public float widthEm; // per button
        public int fontSizeShort;
        public int fontSizePrefab = 10;
        public int fontSizeDisplay;

        public Vector2 scrollPosition = new Vector2(0, 0);
        public string[] groups; // ns "UI.PrefabGroup."

        public PrefabSubpanelSetting(string title, string[] groups, int xCount, int fontSizeShort = 13, int fontSizeDisplay = 10) {
            InstanceAdd(this);
            this.title = title;
            this.groups = groups;
            this.xCount = Math.Max(1, xCount);
            this.widthEm = 43.5f / this.xCount;
            this.fontSizeShort = fontSizeShort;
            this.fontSizeDisplay = fontSizeDisplay;
        }

        public int GetFontSize(int mode) {
            switch (mode) {
            case 1: // prefab
                return fontSizePrefab;
            case 2: // displayName
                return fontSizeDisplay;
            default: // short
                return fontSizeShort;
            }
        }
    }

    private List<PrefabSubpanelSetting>[] prefabSubpanelSetting =
    {
        new List<PrefabSubpanelSetting> // 0 tools: Hand Tools, Equipments, Survival, Suits
        {
            new PrefabSubpanelSetting("HandTools", new string[] { "HandTool", "Weapon", "OtherTool" }, 4),
            new PrefabSubpanelSetting("Equipments", new string[] {  "ToolBelt", "Glasses", "Equipment" }, 3),
            new PrefabSubpanelSetting("Survival", new string[] { "Battery", "Survival", "Canister" }, 3),
            new PrefabSubpanelSetting("Suits", new string[] { "Suit" }, 3),
        },
        new List<PrefabSubpanelSetting> // 1 basics: RawMaterial, Structure, Circuits, Biology, Kits
        {
            new PrefabSubpanelSetting("RawMaterial", new string[] { "Material", "Ingot", "Ore", "Ice" }, 4 ),
            new PrefabSubpanelSetting("Structure", new string[] { "StructureFrame",  "Pipe", "PipeVent", "PipeHeat", "PipeKit" }, 3),
            new PrefabSubpanelSetting("Circuits", new string[] { "Power", "CircuitLogic", "Circuitboard", "PowerGenerator"  }, 3),
            new PrefabSubpanelSetting("Biology", new string[] { "Food", "Seed", "Hydroponics", "Plant" }, 4, 12),
            new PrefabSubpanelSetting("Kits", new string[] { "Fabricator", "Kit" }, 3, 12),
        },
        new List<PrefabSubpanelSetting> // N-1 others
        {
            new PrefabSubpanelSetting("OtherItems", new string[] { "SprayCan", "Other", "Wreckage", "Deprecated" }, 3, 12),
        }
    };
    #endregion

    #region Settings
    private SelectComponent LocaleDiv;
    private KeyboardShortcutComponent ToggleWindowHotkeyDiv;
    private KeyboardShortcutComponent ToggleWindowHotkey2Div;
    private KeyboardShortcutComponent QuickActionToolHotkeyDiv;
    #endregion

    public SspGui() {
        Log.LogInfo("SspGui ctor");
    }

    protected override void _reset() {
        foreach (PrefabSubpanelSetting setting in PrefabSubpanelSetting.AllInstances) {
            setting.scrollPosition.Set(0f, 0f);
        }
        foreach (NavbarComponent navbar in prefabPanelDivs) {
            navbar.SetIndex(0);
        }
        Translation.Locale = SspConfig.Locale.Value;
    }

    protected override void _init() {
        Log.LogInfo("SspGui Initialize");

        windowPos = new Vector2(300, 100);
        windowSize = new Vector2(580, 640);
        windowTexNormal = GetPureColorTexture2D(Color.black);
        windowTexTerminal = GetPureColorTexture2D(new Color(0f, 0f, 0f, .6f));
        windowTitle = $"Stationeers Server Preference  v{PluginInfo.PLUGIN_VERSION}";

        windowPanelDiv = new NavbarComponent(new string[] { "Gameplay", "Terminal", "Tools", "Basics", "OtherItems", "Settings" }, "UI.Panel.", 0, 7f);
        windowPanelDraw = new PanelDrawDelegate[] { DrawDifficultyPanel, DrawTerminalPanel, DrawItemSpawnPanel, DrawItemSpawnPanel, DrawItemSpawnPanel, DrawOthersPanel };
        windowPanelParam = new int[] { 0, 0, 0, 1, prefabSubpanelSetting.Length - 1, 0 };

        BreathingRateDiv = new FloatComponent("UI.Difficulty.BreathingRate", 0f, 15f);
        FoodDecayRateDiv = new FloatComponent("UI.Difficulty.FoodDecayRate", 0f, 10f);
        HungerRateDiv = new FloatComponent("UI.Difficulty.HungerRate", 0f, 10f);
        HydrationRateDiv = new FloatComponent("UI.Difficulty.HydrationRate", 0f, 10f);
        HygieneRateDiv = new FloatComponent("UI.Difficulty.HygieneRate", 0f, 10f);
        JetpackRateDiv = new FloatComponent("UI.Difficulty.JetpackRate", 0f, 10f);
        LungDamageRateDiv = new FloatComponent("UI.Difficulty.LungDamageRate", 0f, 4f);
        MiningYieldDiv = new FloatComponent("UI.Difficulty.MiningYield", 0f, 15f);
        MoodRateDiv = new FloatComponent("UI.Difficulty.MoodRate", 0f, 10f);
        OfflineMetabolismDiv = new FloatComponent("UI.Difficulty.OfflineMetabolism", 0f, 2f);
        RobotBatteryRateDiv = new FloatComponent("UI.Difficulty.RobotBatteryRate", 0f, 10f);
        WeatherLanderDamageRateDiv = new FloatComponent("UI.Difficulty.WeatherLanderDamageRate", 0f, 10f);

        RespawnConditionDiv = new SelectComponent("UI.RespawnCondition", new string[] { "Easy", "Normal", "Stationeer" }, "UI.Respawn.");

        gasModifyMolesDiv = new FloatComponent("UI.Terminal.ModifyGasMoles", 0, 100, FloatComponent.DrawType.DrawSlider | FloatComponent.DrawType.DrawLabelTranslation);
        gasModifyMolesDiv.SetValue(20f);
        gasModifyTemperatureDiv = new FloatComponent("UI.Terminal.ModifyGasTemperature", 1, 3000, FloatComponent.DrawType.DrawInt | FloatComponent.DrawType.DrawLabelTranslation);
        gasModifyTemperatureDiv.SetValue(300f);
        copyLabelStyle = null; // can only call GUI functions from inside OnGUI

        prefabDisplayMode = 0;
        prefabDisplayModeDiv = new SelectComponent("", new string[] { "Short", "Prefab", "Display" }, "UI.PrefabDisplayMode.");
        prefabPanelDivs = new NavbarComponent[] {
            new NavbarComponent(from s in prefabSubpanelSetting[0] select s.title, "UI.Panel."),
            new NavbarComponent(from s in prefabSubpanelSetting[1] select s.title, "UI.Panel."),
        };
        SspPrefab.Initialize();
        UpdatePrefabDisplayName();

        LocaleDiv = new SelectComponent("UI.Locale", Translation.AcceptLocales, "UI.Language.");
        ToggleWindowHotkeyDiv = new KeyboardShortcutComponent("UI.Hotkey.ToggleWindow", SspConfig.ToggleWindowHotkey);
        ToggleWindowHotkey2Div = new KeyboardShortcutComponent("UI.Hotkey.ToggleWindow2", SspConfig.ToggleWindowHotkey2);
        QuickActionToolHotkeyDiv = new KeyboardShortcutComponent("UI.Hotkey.QuickActionTool", SspConfig.GamePlay.QuickActionToolHotkey);

        Log.LogInfo("SspGui Initialize done");
    }

    protected override void _draw_before() {
        windowTex = windowPanelDiv.Index == 1 ? windowTexTerminal : windowTexNormal;
    }

    private void DrawDifficultyPanel(int flag) {
        DifficultySetting d = DifficultySetting.Current;
        SspDifficulty.Setter ds = new SspDifficulty.Setter();
        GUILayout.BeginVertical();

        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Difficulty.Title"), GUILayout.Width(12f * em));
        if (ToggleConfigEntry(SspConfig.Difficulty.Enabled, T("UI.Difficulty.Enabled"), "Difficulty.Enabled")) {
            if (SspConfig.Difficulty.Enabled.Value) {
                SspDifficulty.SetAll(d, !d.Hidden); // copy to new setting if not hidden
                d = DifficultySetting.Current; // refresh value
                BreathingRateDiv.ValueNeedSet = true;
                FoodDecayRateDiv.ValueNeedSet = true;
                HungerRateDiv.ValueNeedSet = true;
                HydrationRateDiv.ValueNeedSet = true;
                HygieneRateDiv.ValueNeedSet = true;
                JetpackRateDiv.ValueNeedSet = true;
                LungDamageRateDiv.ValueNeedSet = true;
                MiningYieldDiv.ValueNeedSet = true;
                MoodRateDiv.ValueNeedSet = true;
                OfflineMetabolismDiv.ValueNeedSet = true;
                RobotBatteryRateDiv.ValueNeedSet = true;
                WeatherLanderDamageRateDiv.ValueNeedSet = true;
                RespawnConditionDiv.ValueNeedSet = true;
            }
        }
        GUILayout.EndHorizontal();

        GUI.enabled = SspConfig.Difficulty.Enabled.Value; // cannot change
        GUILayout.BeginHorizontal();
        ds.Creative = GUILayout.Toggle(d.Creative, T("UI.Difficulty.Creative"));
        ds.EatWhileHelmetClosed = GUILayout.Toggle(d.EatWhileHelmetClosed, T("UI.Difficulty.EatWhileHelmetClosed"));
        ds.DrinkWhileHelmetClosed = GUILayout.Toggle(d.DrinkWhileHelmetClosed, T("UI.Difficulty.DrinkWhileHelmetClosed"));
        GUILayout.EndHorizontal();

        if (BreathingRateDiv.OnGUI(d.BreathingRate)) { ds.BreathingRate = BreathingRateDiv.Value; }
        if (HungerRateDiv.OnGUI(d.HungerRate)) { ds.HungerRate = HungerRateDiv.Value; }
        if (HydrationRateDiv.OnGUI(d.HydrationRate)) { ds.HydrationRate = HydrationRateDiv.Value; }
        if (HygieneRateDiv.OnGUI(d.HygieneRate)) { ds.HygieneRate = HygieneRateDiv.Value; }
        if (JetpackRateDiv.OnGUI(d.JetpackRate)) { ds.JetpackRate = JetpackRateDiv.Value; }
        if (LungDamageRateDiv.OnGUI(d.LungDamageRate)) { ds.LungDamageRate = LungDamageRateDiv.Value; }
        if (MiningYieldDiv.OnGUI(d.MiningYield)) { ds.MiningYield = MiningYieldDiv.Value; }
        if (MoodRateDiv.OnGUI(d.MoodRate)) { ds.MoodRate = MoodRateDiv.Value; }
        if (OfflineMetabolismDiv.OnGUI(d.OfflineMetabolism)) { ds.OfflineMetabolism = OfflineMetabolismDiv.Value; }
        if (RobotBatteryRateDiv.OnGUI(d.RobotBatteryRate)) { ds.RobotBatteryRate = RobotBatteryRateDiv.Value; }
        if (WeatherLanderDamageRateDiv.OnGUI(d.WeatherLanderDamageRate)) { ds.WeatherLanderDamageRate = WeatherLanderDamageRateDiv.Value; }
        if (FoodDecayRateDiv.OnGUI(d.FoodDecayRate)) { ds.FoodDecayRate = FoodDecayRateDiv.Value; }

        GUI.enabled = false; // force false for respawn condition
        if (RespawnConditionDiv.OnGUI(d.RespawnCondition == null ? SspConfig.Difficulty.RespawnCondition.Value : d.RespawnCondition.Id)) { ds.RespawnCondition = RespawnConditionDiv.Value; }
        GUI.enabled = true;

        // human
        GUILayout.Space(2f * em);
        GUILayout.BeginHorizontal();
        ToggleConfigEntry(SspConfig.GamePlay.HumanGodMode, T("UI.GamePlay.HumanGodMode"), "GamePlay.HumanGodMode");

        Human human = Util.GetHuman(true);
        GUI.enabled = human != null;
        if (GUILayout.Button(T("UI.GamePlay.RefreshHuman"), GUILayout.Width(10f * em), GUILayout.Height(2f * em))) {
            Util.RefreshHumanWrap(); // call SspQuickAction.RefreshHuman();
        }
        GUI.enabled = true;
        if (human != null) {
            GUILayout.Label($"{T("UI.GamePlay.FoodQuality")} {human.FoodQuality:N3} {T("UI.GamePlay.Hygiene")} {human.Hygiene:N3} {T("UI.GamePlay.Mood")} {human.Mood:N3}");
        }
        GUILayout.EndHorizontal();

        // weather
        GUILayout.Space(2f * em);
        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Storm"));
        GUI.enabled = SspWeather.HasWeather;
        if (GUILayout.Button(T("UI.Start"), GUILayout.Width(5f * em), GUILayout.Height(2f * em))) {
            SspTerminalCommand.SetWeatherCommand(true);
        }
        if (GUILayout.Button(T("UI.Stop"), GUILayout.Width(5f * em), GUILayout.Height(2f * em))) {
            SspTerminalCommand.SetWeatherCommand(false);
        }
        GUI.enabled = true;
        GUILayout.FlexibleSpace();
        if (SspWeather.HasWeather) {
            if (WeatherManager.IsWeatherEventRunning) {
                GUILayout.Label(T("UI.StormLastFor") + $" {SspWeather.WeatherTimeRemaining:F1}s");
            } else if (WeatherManager.IsWeatherEventScheduled) {
                GUILayout.Label($"Starts after {WeatherManager.GetSecondsWhenNextWeatherEventIsActive():F1}s Duration: {WeatherManager.WeatherEventLength:F0}s");
            } else {
                GUILayout.Label(T("UI.NoStormCurrent"));
            }
        } else {
            GUILayout.Label(T("UI.NoWeather"));
        }
        GUILayout.EndHorizontal();
        if (SspWeather.HasWeather) {
            GUILayout.BeginHorizontal();
            GUILayout.Label($"{T("UI.DaysPast")}: {WorldManager.DaysPast}");
            GUILayout.Label($"{T("UI.DaysSinceLastWeatherEvent")}: {WeatherManager.DaysSinceLastWeatherEvent}");
            GUILayout.EndHorizontal();
        }

        GUILayout.EndVertical();
    }

    private void DrawTerminalPanel(int flag) {
        GUILayout.BeginVertical();
        bool guiEnabled = GUI.enabled;

        // device config
        if (copyLabelStyle == null) {
            copyLabelStyle = new GUIStyle(GUI.skin.button);
            copyLabelStyle.normal.background = GUI.skin.label.normal.background;
        }

        SspDeviceConfig.OnUpdate(); // call update
        SspDeviceConfig.ConfigText cText = SspDeviceConfig.Instance.text;
        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Terminal.CursorThing"), GUILayout.Width(6f * em));
        GUI.enabled = SspDeviceConfig.Instance.thing != null;
        if (GUILayout.Button(cText.name, copyLabelStyle)) {
            GUIUtility.systemCopyBuffer = cText.name;
        }
        if (GUILayout.Button(cText.referenceId, copyLabelStyle, GUILayout.Width(10f * em))) {
            GUIUtility.systemCopyBuffer = cText.referenceId;
        }
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label("Prefab", GUILayout.Width(6f * em));
        GUI.enabled = SspDeviceConfig.Instance.thing != null;
        if (GUILayout.Button(cText.prefabName, copyLabelStyle)) {
            GUIUtility.systemCopyBuffer = cText.prefabName;
        }
        if (GUILayout.Button(cText.prefabHash, copyLabelStyle, GUILayout.Width(10f * em))) {
            GUIUtility.systemCopyBuffer = cText.prefabHash;
        }
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Terminal.Logic"), GUILayout.Width(6f * em), GUILayout.Height(3f * em));
        DrawAligmentText(cText.logic, TextAnchor.MiddleLeft, null, GUILayout.Width(windowSize.x - 10f * em));
        GUILayout.EndHorizontal();
        GUI.enabled = guiEnabled;

        // atmosphere
        SspAtmosphere.OnUpdate(); // call update
        SspAtmosphere.AtmosphereText aText = SspAtmosphere.Instance.text; // statistics
        // name
        GUILayout.BeginHorizontal();
        GUILayout.Label(T("Atmosphere.Atmosphere"));
        GUILayout.Label(aText.name);
        ToggleConfigEntry(SspConfig.Terminal.DisplayAllGas, T("Details"), "DisplayAllGas");
        GUILayout.EndHorizontal();

        Atmosphere atmosphere = SspAtmosphere.Instance.atmosphere;
        GUI.enabled = atmosphere != null;
        GUILayout.BeginHorizontal();
        // part 1
        GUILayout.BeginVertical(); GUILayout.Label(T("Atmosphere.Pressure")); GUILayout.Label(T("Atmosphere.Converted")); GUILayout.EndVertical();
        GUILayout.BeginVertical(); GUILayout.Label(aText.pressure); GUILayout.Label(aText.converted); GUILayout.EndVertical();
        // part 2
        GUILayout.BeginVertical(); GUILayout.Label(T("Atmosphere.Temperature")); GUILayout.Label(T("Atmosphere.Radiated")); GUILayout.EndVertical();
        GUILayout.BeginVertical(); GUILayout.Label(aText.temperature); GUILayout.Label(aText.radiated); GUILayout.EndVertical();
        // part 3
        GUILayout.BeginVertical(); GUILayout.Label(T("Atmosphere.Capacity")); GUILayout.Label(T("Atmosphere.Latent")); GUILayout.EndVertical();
        GUILayout.BeginVertical(); GUILayout.Label(aText.capacity); GUILayout.Label(aText.latent); GUILayout.EndVertical();
        // part 4
        GUILayout.BeginVertical(); GUILayout.Label(T("Atmosphere.Liquid")); GUILayout.Label(T("Atmosphere.Stress")); GUILayout.EndVertical();
        GUILayout.BeginVertical(); GUILayout.Label(aText.liquid); GUILayout.Label(aText.stress); GUILayout.EndVertical();
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Terminal.Atmosphere"));
        if (GUILayout.Button(T("UI.Terminal.RemoveGas"), GUILayout.Width(9f * em), GUILayout.Height(2f * em))) {
            SspTerminalCommand.ClearGasCommand(atmosphere);
        }
        GUILayout.EndHorizontal();
        GUI.enabled = guiEnabled;

        if (SspConfig.Terminal.DisplayAllGas.Value) {
            if (atmosphere != null) {
                // table
                float lineHeight = 1.75f * em;
                GUILayout.BeginHorizontal();
                GUILayout.BeginVertical();
                GUILayout.Label(T("UI.Terminal.Type"));
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.name);
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label(T("Atmosphere.Gas")); // modify gas
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    if (g.gasType == Chemistry.GasType.Undefined) {
                        // GUILayout.Label("x");
                        continue;
                    }
                    GUILayout.BeginHorizontal();
                    if (GUILayout.Button("+", GUILayout.Width(lineHeight), GUILayout.Height(lineHeight))) {
                        SspTerminalCommand.AddGasCommand(atmosphere, g.gasType, gasModifyMolesDiv.Value, gasModifyTemperatureDiv.Value);
                    }
                    if (GUILayout.Button("-", GUILayout.Width(lineHeight), GUILayout.Height(lineHeight))) {
                        SspTerminalCommand.RemoveGasCommand(atmosphere, g.gasType, gasModifyMolesDiv.Value);
                    }
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label(T("UI.Terminal.Ratio"));
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.gasPercent);
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label("Moles");
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.gasMoles);
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label(T("Atmosphere.Liquid"));
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    if (g.liquidType == Chemistry.GasType.Undefined) {
                        GUILayout.Label("x");
                        continue;
                    }
                    GUILayout.BeginHorizontal();
                    if (GUILayout.Button("+", GUILayout.Width(lineHeight), GUILayout.Height(lineHeight))) {
                        SspTerminalCommand.AddGasCommand(atmosphere, g.liquidType, gasModifyMolesDiv.Value, gasModifyTemperatureDiv.Value);
                    }
                    if (GUILayout.Button("-", GUILayout.Width(lineHeight), GUILayout.Height(lineHeight))) {
                        SspTerminalCommand.RemoveGasCommand(atmosphere, g.liquidType, gasModifyMolesDiv.Value);
                    }
                    GUILayout.EndHorizontal();
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label(T("UI.Terminal.Ratio"));
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.liquidPercent);
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label("Moles");
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.liquidMoles);
                }
                GUILayout.EndVertical();

                GUILayout.BeginVertical();
                GUILayout.Label(T("UI.Terminal.Volume"));
                foreach (SspAtmosphere.GasText g in aText.mix) {
                    GUILayout.Label(g.liquidVolume);
                }
                GUILayout.EndVertical();
                GUILayout.EndHorizontal();
            }

            // modify settings
            GUILayout.BeginHorizontal();
            GUILayout.Label("+/-");
            GUILayout.BeginVertical();
            gasModifyMolesDiv.OnGUI(gasModifyMolesDiv.Value);
            GUILayout.EndVertical();
            GUILayout.Space(2f * em);
            GUILayout.BeginVertical();
            gasModifyTemperatureDiv.OnGUI(gasModifyTemperatureDiv.Value);
            GUILayout.EndVertical();
            GUILayout.EndHorizontal();
        }

        GUILayout.EndVertical();
    }

    private void DrawItemSpawnPanel(int flag) {
        if (flag < 0 || flag >= prefabSubpanelSetting.Length) {
            Log.LogError($"DrawItemSpawnPanel invalid flag {flag}");
        }
        bool isOtherPanel = flag == prefabSubpanelSetting.Length - 1;
        // subpanel navbar 
        if (!isOtherPanel) {
            prefabPanelDivs[flag].OnGUI();
        }
        // display mode selection
        GUILayout.BeginVertical();
        if (prefabDisplayModeDiv.OnGUI(prefabDisplayMode)) {
            prefabDisplayMode = prefabDisplayModeDiv.Index;
        }
        GUILayout.EndVertical();

        // subpanel
        int subpanel = isOtherPanel ? 0 : prefabPanelDivs[flag].Index;
        DrawPrefabScrollView(prefabSubpanelSetting[flag][subpanel]);
    }

    private void DrawPrefabScrollView(PrefabSubpanelSetting setting) {
        GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
        buttonStyle.padding = new RectOffset(0, 0, 1, 1);
        buttonStyle.fontSize = setting.GetFontSize(prefabDisplayMode);

        setting.scrollPosition = GUILayout.BeginScrollView(setting.scrollPosition, GUILayout.Width(windowSize.x - 1.7f * em), GUILayout.Height(windowSize.y - 9f * em));
        GUILayout.BeginVertical(GUILayout.Width(windowSize.x - 4.2f * em));
        foreach (string groupName in setting.groups) {
            // draw group name for multiple groups
            if (setting.groups.Length > 1) {
                GUILayout.Label(Translation.T("UI.PrefabGroup." + groupName, groupName));
            }
            SspPrefab.Group group = SspPrefab.FindGroup(groupName);
            if (group == null || group.prefabs.Count == 0) {
                continue; // should not be null
            }

            // draw group
            int i = 0;
            foreach (string prefab in group.prefabs) {
                if (i % setting.xCount == 0) {
                    GUILayout.BeginHorizontal();
                }
                string displayName = prefab; // Prefab
                if (prefabDisplayMode == 2) {
                    displayName = prefabDisplayNames[prefab]; // Display
                } else if (prefabDisplayMode == 0) {
                    // Short: display short name
                    string transId = "Prefab." + prefab;
                    displayName = Translation.Contains(transId) ? T(transId) : prefabDisplayNames[prefab];
                }
                if (GUILayout.Button(displayName, buttonStyle, GUILayout.Width(setting.widthEm * em), GUILayout.Height(2f * em))) {
                    ConsoleWindow.Print(prefab, ConsoleColor.Yellow);
                    long human = Util.GetHumanRefId();
                    if (human != -1L) {
                        Log.LogInfo($"Spawn {prefab} @ {human}");
                        // send spawn command to game
                        OnServer.SpawnDynamicThingMaxStack(human, prefab);
                        // (optional) also set ImguiCreativeSpawnMenu (cheating menu) state
                        InventoryManager.SpawnPrefab = Prefab.Find<DynamicThing>(prefab);
                    }
                }
                i++;
                if (i % setting.xCount == 0) {
                    GUILayout.EndHorizontal();
                }
            }
            if (i % setting.xCount != 0) {
                GUILayout.EndHorizontal();
            }
            GUILayout.Space(0.5f * em);
        }
        GUILayout.EndVertical();
        GUILayout.EndScrollView();
    }

    private void DrawOthersPanel(int flag) {
        GUILayout.BeginVertical();

        if (LocaleDiv.OnGUI(SspConfig.Locale.Value)) {
            Log.LogInfo($"Locale set {LocaleDiv.Value}");
            SspConfig.Locale.Value = LocaleDiv.Value;
            Translation.Locale = LocaleDiv.Value;
        }

        if (ToggleConfigEntry(SspConfig.OverrideLocalization, T("UI.OverrideLocalization"), "OverrideLocalization")) {
            if (SspConfig.OverrideLocalization.Value) {
                SspLocalization.Run();
            }
        }

        if (ToggleWindowHotkeyDiv.OnGUI()) {
            SspConfig.ToggleWindowHotkey.Value = ToggleWindowHotkeyDiv.Value;
        }
        if (ToggleWindowHotkey2Div.OnGUI()) {
            SspConfig.ToggleWindowHotkey2.Value = ToggleWindowHotkey2Div.Value;
        }

        ToggleConfigEntry(SspConfig.GamePlay.QuickAction, T("UI.GamePlay.QuickAction"), "GamePlay.QuickAction");
        if (QuickActionToolHotkeyDiv.OnGUI()) {
            SspConfig.GamePlay.QuickActionToolHotkey.Value = QuickActionToolHotkeyDiv.Value;
        }

        // debug
        GUILayout.Space(2f * em);
        GUILayout.BeginHorizontal();
        GUILayout.Label(T("UI.Dump"));
        if (GUILayout.Button("DynThings", GUILayout.Width(7f * em), GUILayout.Height(2f * em))) {
            Dumper.DynamicThingDumper.Clipboard();
        }
        if (GUILayout.Button("Things", GUILayout.Width(5f * em), GUILayout.Height(2f * em))) {
            Dumper.PrefabDumper.Clipboard();
        }
        if (GUILayout.Button("Difficulty", GUILayout.Width(5f * em), GUILayout.Height(2f * em))) {
            Dumper.DifficultyDumper.Clipboard();
        }
        GUILayout.EndHorizontal();
        GUILayout.EndVertical();
    }

    private Texture2D GetPureColorTexture2D(Color color) {
        Texture2D texture = new Texture2D(16, 16);
        texture.SetPixels(Enumerable.Repeat(color, 256).ToArray());
        texture.Apply();
        return texture;
    }

    public void UpdatePrefabDisplayName() {
        Log.LogInfo("SspGui UpdatePrefabDisplayName");
        if (prefabDisplayNames.Count > 0) {
            prefabDisplayNames.Clear();
        }
        foreach (string name in InventoryManager.DynamicThingPrefabs) {
            prefabDisplayNames.Add(name, Prefab.Find<DynamicThing>(name)?.DisplayName);
        }
    }
}
